#include<iostream>
#include "Ast.hh"
#include "Error.hh"
#define BUFFSIZE 1024

extern Error *error;
extern ClassEntity *objectclass;

ClassEntity *currClass = NULL;
MethodEntity *currMethod = NULL;
ConstructorEntity *currConstructor = NULL;

char errorMessage[BUFFSIZE];

void initialize_typechecker() {
  // initialize any needed variables here...
}

void ClassEntity::typecheck() {
   	currClass = this;
   	list<Entity*>::iterator itBegin = class_members_->begin();
   	list<Entity*>::iterator itEnd = class_members_->end();
	while(itBegin != itEnd)
	{
		if((*itBegin)->kind() == METHOD_ENTITY)
			static_cast<MethodEntity*>(*itBegin)->typecheck();
		if((*itBegin)->kind() == CONSTRUCTOR_ENTITY)
			static_cast<ConstructorEntity*>(*itBegin)->typecheck();
		itBegin++;
	}
}

void MethodEntity::typecheck() {
	currMethod = this;
	method_body_->typecheck();
	currMethod = NULL;
}

void ConstructorEntity::typecheck() {
	currConstructor =  this;
	constructor_body_->typecheck();
	currConstructor = NULL;
}

// Typecheck method for IfStatement
void IfStatement::typecheck() {
	Type *type = expr_->typeinfer();
	if(type->kind() == BOOLEAN_TYPE)
	{
		thenpart_->typecheck();
		elsepart_->typecheck();
		return;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " If statement boolean expression expected" << endl;
}

// Typecheck method for WhileStatement
void WhileStatement::typecheck() {
	Type *type = expr_->typeinfer();
	if(type->kind() == BOOLEAN_TYPE)
	{
		body_->typecheck();
		return;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " While statement boolean expression expected" << endl;
}

// Typecheck method for ForStatement
void ForStatement::typecheck() {
	Type *type = guard_->typeinfer();
	if(type->kind() == BOOLEAN_TYPE)
	{
		update_->typecheck();
		body_->typecheck();
		return;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " For statement boolean expression expected" << endl;
}

// Typecheck method for ReturnStatement
void ReturnStatement::typecheck() {
	Type *type = NULL;
	if(currConstructor == NULL)
	{
		type = expr_->typeinfer();
		if(type->isSubtypeOf(currMethod->return_type()))
			return;
	}
	else 
	{
		cout << "Error(" << lineno_ << "): constructor cannot contain return statement" << endl;
		return;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " Return statement type ";
	currMethod->return_type()->print();
	cout << " expected found ";
	type->print();
	cout << endl;
}


// Typecheck method for BlockStatement
void BlockStatement::typecheck() {
	list<Statement*>::iterator itBegin = stmt_list_->begin();
	list<Statement*>::iterator itEnd = stmt_list_->end();
	while(itBegin != itEnd)
	{
		(*itBegin)->typecheck();
		itBegin++;
	}
}

// Typecheck method for ExprStatement
void ExprStatement::typecheck() {
	expr_->typeinfer();
}

// Typecheck method for DeclStatement
void DeclStatement::typecheck() {
	return;
}

// Typecheck method for BreakStatement
void BreakStatement::typecheck() {
	return;
}

// Typecheck method for ContinueStatement
void ContinueStatement::typecheck() {
	return;
}

// Typecheck method for SkipStatement
void SkipStatement::typecheck() {
	return;
}

// Typeinfer method for BinaryExpression
Type* BinaryExpression::typeinfer() {
	Type *typeLeft  = lhs_->typeinfer();
	Type *typeRight = rhs_->typeinfer();
	Type *type = NULL;
	bool leftIsNumeric = ( (typeLeft->kind() == FLOAT_TYPE) || (typeLeft->kind() == INT_TYPE) );
	bool rightIsNumeric = ( (typeRight->kind() == FLOAT_TYPE) || (typeRight->kind() == INT_TYPE) );
	
	if( (typeLeft->kind() == ERROR_TYPE) ) 
		return typeLeft;
	if( (typeRight->kind() == ERROR_TYPE) )
		return typeRight;

	if( leftIsNumeric && rightIsNumeric )
	{
		bool isArithmetic = (binary_operator_ == ADD) || (binary_operator_ == SUB) || (binary_operator_ == DIV) || (binary_operator_ == MUL) ;
		if( isArithmetic )
		{
			if( (typeLeft->kind() == INT_TYPE) && (typeRight->kind() == INT_TYPE) )
				return ( new IntType() );
			else
				return ( new FloatType() );
		}
		bool isRelational = (binary_operator_ == LT) || (binary_operator_ == LEQ) || (binary_operator_ == GT) || (binary_operator_ == GEQ) ;
		if( isRelational )
			return new BooleanType();
	}
	if( (binary_operator_ == EQ) || (binary_operator_ == NEQ) )
	{
		if(typeLeft->isSubtypeOf(typeRight) || typeRight->isSubtypeOf(typeLeft))
			return new BooleanType();
	}
	if( (binary_operator_ == AND) || (binary_operator_ == OR) )
		return new BooleanType();
	const char *ch = NULL;
	switch(binary_operator_)
	{
		case ADD:
			ch = "+";
		break;
		case SUB:
			ch = "-";
		break;
		case DIV:
			ch = "/";
		break;
		case MUL:
			ch = "*";
		break;
		case LT:
			ch = "<";
		break;
		case LEQ:
			ch = "<=";
		break;
		case GT:
			ch = ">";
		break;
		case GEQ:
			ch = ">=";
		break;
		case EQ:
			ch = "==";
		break;
		case NEQ:
			ch = "!=";
		break;
		case AND:
			ch = "&&";
		break;
		case OR:
			ch = "||";
		break;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " Invalid use of operator " << ch;
	cout << " on operands of type ";
	typeLeft->print(); 
	cout << " and type ";
	typeRight->print(); 
	cout << endl; 
   	return(new ErrorType());
}

// Typeinfer method for AssignExpression
Type* AssignExpression::typeinfer() {
	Type *typeLeft = lhs_->typeinfer();
	Type *typeRight = rhs_->typeinfer();
	if( (typeLeft->kind() == ERROR_TYPE) )
		return typeLeft;
	if( (typeRight->kind() == ERROR_TYPE) )
		return typeRight;
	if(typeRight->isSubtypeOf(typeLeft))
		return typeRight;
	cout << "Error(" << lineno_ << "):";
	cout << " assign expression type mismatch expected ";
	typeLeft->print();
	cout << " found ";
	typeRight->print();
	cout << endl;
	return(new ErrorType());
}

// Typeinfer method for ArrayAccess
Type* ArrayAccess::typeinfer() {
  	Type *baseType = base_->typeinfer();
	Type *indexType = idx_->typeinfer();
	if( (baseType->kind() == ERROR_TYPE) )
		return baseType;
	if( (indexType->kind() == ERROR_TYPE) )
		return indexType;
	if( ( baseType->kind() == ARRAY_TYPE ) && ( indexType->kind() == INT_TYPE ) )
		return (static_cast<ArrayType *>(baseType))->elementtype();
	cout << "Error(" << lineno_ << "): ";
	if( baseType->kind() != ARRAY_TYPE )
	{
		baseType->print();
		cout << " not an array type" << endl;
	}
	if( indexType->kind() != INT_TYPE )
		cout << "array index should be of type int" << endl;
	return(new ErrorType());
}


// Typeinfer method for FieldAccess:
Type* FieldAccess::typeinfer() {
	Type *type = base_->typeinfer();
	bool staticAccess = false;
	bool privateCheck = false;
	bool isSuper = false; 
	ClassEntity *fieldClass = NULL;
	if( type->kind() == CLASS_TYPE )
	{	
		fieldClass = static_cast<ClassType *>(type)->classtype();
		staticAccess = true;
	}
	else if ( type->kind() == INSTANCE_TYPE )
	{
		fieldClass = static_cast<InstanceType *>(type)->classtype();
		staticAccess = false;
	}
	else
   		return(new ErrorType());
	const char *fieldname = this->name_;
	// Check if current class is same as the classEntity
	if( currClass != fieldClass )
		privateCheck = true;
	while( NULL != fieldClass )
	{
		list<Entity *> *classMembers = fieldClass->class_members();
		if( NULL != classMembers )
		{
			list<Entity *>::iterator itBegin = classMembers->begin();
			list<Entity *>::iterator itEnd = classMembers->end();
			while(itBegin != itEnd)
			{
				if( FIELD_ENTITY == (*itBegin)->kind() )
				{
					const char *name = (*itBegin)->name();
					if( !strcmp(fieldname, name)  )
					{
						FieldEntity *entity = static_cast<FieldEntity *>(*itBegin);
						bool fieldIsVisible = entity->visibility_flag();
						bool fieldIsStatic = entity->static_flag();
						if( NULL != entity )
						{
							bool pass = true;
							/*
							** If field accessed is private; Check if the class it is present
							** in is different than the current class or is super class 
							*/
							if( !fieldIsVisible && ( isSuper || privateCheck ) )
							{
								sprintf(errorMessage, "Error(%d): Field %s is not visible in class %s", lineno_, fieldname, fieldClass->name()); 
								pass = false;
							}
							/*
							** Check if the field access is static or the  current method is static
							** but the field accessed is actually not static.
							*/
							if( staticAccess && !fieldIsStatic ) 
							{
								sprintf(errorMessage, "Error(%d): Field %s is not static in class %s", lineno_, fieldname, fieldClass->name()); 
								pass =  false;
							}
							if( true == pass )
								return entity->type();
							else
							{
   								error->implementation_error(errorMessage);
   								return(new ErrorType());
							}
						}
					}
				}
				itBegin++;
			}
		}
		fieldClass = fieldClass->superclass();
		isSuper = true;
	}
	sprintf(errorMessage, "Error(%d): Field %s is not present in class %s", lineno_, fieldname, fieldClass->name()); 
   	error->implementation_error(errorMessage);
	return(new ErrorType());
}

// Typeinfer method for MethodInvocation
Type* MethodInvocation::typeinfer() {
	Type *type = base_->typeinfer();
	bool staticAccess = false;
	bool privateCheck = false;
	bool isSuper = false; 
	ClassEntity *methodClass = NULL;
	if( type->kind() == CLASS_TYPE )
	{	
		methodClass = static_cast<ClassType *>(type)->classtype();
		staticAccess = true;
	}
	else if ( type->kind() == INSTANCE_TYPE )
	{
		methodClass = static_cast<InstanceType *>(type)->classtype();
		staticAccess = false;
	}
	else
   		return(new ErrorType());
	const char *methodName = this->name_;
	list<Expression *> *actualArgs = this->args_;
	// Check if current class is same as the classEntity
	if( currClass != methodClass )
		privateCheck = true;
	while( NULL != methodClass )
	{
		list<Entity *> *classMembers = methodClass->class_members();
		if( NULL != classMembers )
		{
			list<Entity *>::iterator itBegin = classMembers->begin();
			list<Entity *>::iterator itEnd = classMembers->end();
			while(itBegin != itEnd)
			{
				if( METHOD_ENTITY == (*itBegin)->kind() )
				{
					MethodEntity *entity = static_cast<MethodEntity *>(*itBegin);
					bool methodMatched = true;
					const char *name = entity->name();
					list<Entity *> *formalArgs = entity->formal_params();
					// Check for the suitable match
					if( strcmp(name, methodName) )
						methodMatched = false;
					if( formalArgs->size() != actualArgs->size() )
						methodMatched = false;
					else
					{
						list<Entity *>::iterator formalArgBegin = formalArgs->begin();
						list<Entity *>::iterator formalArgEnd = formalArgs->end();
						list<Expression *>::iterator actArgBegin = actualArgs->begin();
						while(formalArgBegin != formalArgEnd)
						{
							Type *type = (*actArgBegin)->typeinfer();
							if(type->kind() == ERROR_TYPE)
								return type;
							if(!type->isSubtypeOf((static_cast<VariableEntity *>(*formalArgBegin))->type()))
							{
								methodMatched = false;
								break;
							}
							formalArgBegin++;
							actArgBegin++;
						}
					}
					if( methodMatched )
					{
						bool memberIsVisible = entity->visibility_flag();
						bool memberIsStatic = entity->static_flag();
						if( NULL != entity )
						{
							bool pass = true;
							/*
							** If field accessed is private; Check if the class it is present
							** in is different than the current class or is super class 
							*/
							if( !memberIsVisible && ( isSuper || privateCheck ) )
							{
								sprintf(errorMessage, "Error(%d): Method %s is not visible in class %s", lineno_, methodName, methodClass->name()); 
								pass = false;
							}
							/*
							** Check if the field access is static or the  current method is static
							** but the field accessed is actually not static.
							*/
							if( staticAccess &&  !memberIsStatic )
							{
								sprintf(errorMessage, "Error(%d): Method %s is not static in class %s", lineno_, methodName, methodClass->name()); 
								pass =  false;
							}
							if( true == pass )
								return entity->return_type();
							else
							{
   								error->implementation_error(errorMessage);
   								return(new ErrorType());
							}
						}
					}
				}
				itBegin++;
			}
		}
		methodClass = methodClass->superclass();
		isSuper = true;
	}
	sprintf(errorMessage, "Error(%d): Method %s is not present in class %s", lineno_, methodName, methodClass->name()); 
   	error->implementation_error(errorMessage);
   	return(new ErrorType());
}

// Typeinfer method for UnaryExpression
Type* UnaryExpression::typeinfer() {
	Type *type = arg_->typeinfer();
	if( type->kind() == ERROR_TYPE )
		return type;
	if((unary_operator_ == UMINUS) && (type->kind() == INT_TYPE))
		return type;
	else if((unary_operator_ == NEG) && (type->kind() == BOOLEAN_TYPE))
		return type;
	const char *ch = NULL;
	switch(unary_operator_)
	{
		case UMINUS:
			ch = "-";
		break;
		case NEG:
			ch = "!";
		break;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " invalid type ";
	type->print();
	cout << " for operator " << ch << endl;
   	return(new ErrorType());
}

// Typeinfer method for AutoExpression
Type* AutoExpression::typeinfer() {
   Type *type = arg_->typeinfer();
   if( type->kind() == ERROR_TYPE )
		return type;
   if(type->kind() == INT_TYPE)
		return type;
	const char *ch = NULL;
	switch(auto_operator_)
	{
		case PRE_INCR:
			ch = "++";
		break;
		case POST_INCR:
			ch = "++";
		break;
		case PRE_DECR:
			ch = "--";
		break;
		case POST_DECR:
			ch = "--";
		break;
	}
	cout << "Error(" << lineno_ << "):";
	cout << " invalid type ";
	type->print();
	cout << " for operator " << ch << endl;
   	return(new ErrorType());
}

// Typeinfer method for NewArrayInstance: 
Type* NewArrayInstance::typeinfer() {
	list<Expression *>::iterator boundBegin = bounds_->begin();
	list<Expression *>::iterator boundEnd = bounds_->end();
	bool pass = true;
	while(boundBegin != boundEnd)
	{
		Type *type = (*boundBegin)->typeinfer();
		if( type->kind() != INT_TYPE )
		{
			pass = false;
			break;
		}
		boundBegin++;
	
	}
	if(pass)
	{
		Type *type = type_;
		for(int i = 0; i < dimension_; i++)
			type = new ArrayType(type);
		return type;
	}
	else
	{
		cout << "Error(" << lineno_ << "):";
   		cout << " index for array allocation should be int" << endl;
		return(new ErrorType());
	}
}

// Typeinfer method for NewInstance:
Type* NewInstance::typeinfer() {
	bool privateCheck = false;
	list<Expression *> *actualArgs = this->args_;
	// Check if current class is same as the classEntity
	if( currClass != class_entity_ )
		privateCheck = true;
	list<Entity *> *classMembers = class_entity_->class_members();
	if( NULL != classMembers )
	{
		list<Entity *>::iterator itBegin = classMembers->begin();
		list<Entity *>::iterator itEnd = classMembers->end();
		while(itBegin != itEnd)
		{
			if( CONSTRUCTOR_ENTITY == (*itBegin)->kind() )
			{
				ConstructorEntity *entity = static_cast<ConstructorEntity *>(*itBegin);
				bool methodMatched = true;
				list<Entity *> *formalArgs = entity->formal_params();
				// Check for the suitable match
				if( formalArgs->size() != actualArgs->size() )
					methodMatched = false;
				else
				{
					list<Entity *>::iterator formalArgBegin = formalArgs->begin();
					list<Entity *>::iterator formalArgEnd = formalArgs->end();
					list<Expression *>::iterator actArgBegin = actualArgs->begin();
					while(formalArgBegin != formalArgEnd)
					{
						Type *type = (*actArgBegin)->typeinfer();
						if(type->kind() == ERROR_TYPE)
							return type;
						if(!type->isSubtypeOf((static_cast<VariableEntity *>(*formalArgBegin))->type()))
						{
								methodMatched = false;
								break;
						}
						formalArgBegin++;
						actArgBegin++;
					}
				}
				if( methodMatched )
				{
					bool memberIsVisible = entity->visibility_flag();
					if( NULL != entity )
					{
						bool pass = true;
						/*
						** If field accessed is private; Check if the class it is present
						** in is different than the current class 
						*/
						if( !memberIsVisible && privateCheck )
						{	
							sprintf(errorMessage, "Error(%d): Constructor is not visible in class %s", lineno_, class_entity_->name()); 
							pass = false;
						}
						if(pass)
							return new InstanceType(class_entity_);
						else
						{
   							error->implementation_error(errorMessage);
   							return(new ErrorType());
						}
					}
				}
			}
			itBegin++;
		}
	}
	sprintf(errorMessage, "Error(%d): Constructor is not present in class %s", lineno_, class_entity_->name()); 
   	error->implementation_error(errorMessage);
   	return(new ErrorType());
}


// Typeinfer method for ThisExpression
Type* ThisExpression::typeinfer() {
	Type *type = NULL;
	if( currClass != NULL )
	{
		if( currConstructor != NULL )
			type = new InstanceType(currClass);
		else if( currMethod != NULL )
		{
			if( true == currMethod->static_flag() )
				type = new ClassType(currClass);
			else
				type = new InstanceType(currClass);
		}
	}
	if( NULL != type )
		return type;
	else
		return new ErrorType();
}


// Typeinfer method for SuperExpression
Type* SuperExpression::typeinfer() {
	Type *type = NULL;
	ClassEntity *currSuper = currClass->superclass();
	if( currSuper != NULL )
	{
		if( currConstructor != NULL )
		{
			if( NULL != currSuper )
				type = new InstanceType(currSuper);
		}
		else if( currMethod != NULL )
		{
			if( NULL != currSuper )
			{
				if( true == currMethod->static_flag() )
					type = new ClassType(currSuper);
				else
					type = new InstanceType(currSuper);
			}
		}
	}
	if( NULL == type )
   		return(new ErrorType());
	else
		return type;
}

// Typeinfer method for IdExpression
Type* IdExpression::typeinfer() {
	Type *type = NULL;
	if( id_->kind() == VARIABLE_ENTITY )
	{
		VariableEntity *entity = static_cast<VariableEntity *>(id_);
		type = entity->type();
	}
	else if( id_->kind() == CLASS_ENTITY ) 
	{
		ClassEntity *entity = static_cast<ClassEntity *>(id_);
		type = new ClassType(entity);
	}
	else if( id_->kind() == FIELD_ENTITY )
	{
		FieldEntity *entity = static_cast<FieldEntity *>(id_);
		type = entity->type();
	}
	if( NULL == type )
		return ( new ErrorType());
	else
		return type;
}


// Typeinfer method for NullExpression
Type* NullExpression::typeinfer() {
	Type *type = new NullType();
	if( NULL == type )
		return ( new ErrorType());
	else
		return type;
}


// Typeinfer method for BooleanConstant
Type* BooleanConstant::typeinfer() {
	Type *type = new BooleanType();
	if( NULL == type )
		return ( new ErrorType());
	else
		return type;
}

// Typeinfer method for IntegerConstant
Type* IntegerConstant::typeinfer() {
	Type *type = new IntType();
	if( NULL == type )
		return ( new ErrorType());
	else
		return type;
}

// Typeinfer method for FloatConstant
Type* FloatConstant::typeinfer() {
	Type *type = new FloatType();
	if( NULL == type )
		return ( new ErrorType());
	else
		return type;
}


// Typeinfer method for StringConstant
Type* StringConstant::typeinfer() {
	Type *type = new StringType();
	if( NULL == type )
		return ( new ErrorType());
	else
		return type;
}


